package com.dyz.wsw.filter;

import com.dyz.wsw.common.consts.web.HttpConst;
import com.dyz.wsw.common.consts.web.JwtConst;
import com.dyz.wsw.common.enumerator.ServiceCode;
import com.dyz.wsw.common.pojo.auth.Principal;
import com.dyz.wsw.common.utils.security.JWTs;
import com.dyz.wsw.common.utils.security.MD5;
import com.dyz.wsw.common.web.JsonResult;
import com.dyz.wsw.config.WswProperties;
import com.google.gson.Gson;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.net.InetSocketAddress;
import java.util.List;
import java.util.Optional;

/**
 * jwt过滤器,逻辑如下：
 * 1.对于一些放行的请求，就无需jwt校验
 * 2.对需要jwt校验的请求
 *  a)校验jwt的合法性，失败则直接返回401
 *  b)通过则从jwt取出id，并传递给下一个过滤器
 * @author dyz
 * Time 2024/7/21 11:53
 */
@Component
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class JwtFilter implements WebFilter, HttpConst, JwtConst {


    private final WswProperties wswProperties;

    @Autowired
    public JwtFilter(WswProperties wswProperties){
        log.debug("创建jwt过滤器：JwtFilter");
        this.wswProperties = wswProperties;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain){
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        String ip = getIp(request);
        log.debug("ip: {},请求路径: {}",ip,path);

        //1.放行允许的请求，无需jwt校验
        if(release(wswProperties.getPermitUri(),path)){
            return chain.filter(exchange);
        }

        //2.简单校验jwt，如果失败，直接快速失败返回401
        HttpHeaders headers = request.getHeaders();
        log.debug("headers: {}",headers);

        String token = getToken(headers);
        log.debug("token: {}",token);

        ServerHttpResponse response = exchange.getResponse();
        if(!StringUtils.hasText(token)||token.length()<JWT_MIN_LENGTH){
            log.warn("ip: {},本次请求没有携带有效的jwt,拒绝服务!",ip);

            return reject(JsonResult.fail(ServiceCode.ERROR_UNAUTHORIZED,"No Auth"),response);
        }

        //3.进一步校验jwt
        Claims claims = parseToken(token);
        if(claims==null){
            return reject(JsonResult.fail(ServiceCode.ERROR_JWT_FORMAT,"Illegal Jwt"),response);
        }

        //4.校验jwt是否存在盗用
        String jti = claims.get(CLAIM_JWT_JTI).toString();
        String agent = headers.getFirst(HEADER_USER_AGENT);
        if(!checkSteal(ip,jti,agent)){
            log.warn("ip: {},jwt存在盗用风险,拒绝服务!",ip);
            return reject(JsonResult.fail(ServiceCode.ERROR_JWT_SIGNATURE,"Jwt was stolen"),response);
        }

        //5.以上都没问题，那么就放行
        //然后将解析出来的数据交给后续组件处理
        log.debug("jwt校验完成,即将放行!");
        Principal principal = new Principal();
        principal.setId(Long.parseLong(claims.get(CLAIM_USER_ID).toString()));
        principal.setUsername(claims.get(CLAIM_USER_NAME).toString());
//        exchange.getAttributes().put(PRINCIPAL,principal);

        return chain.filter(exchange);
    }

    /**
     * 检查jwt盗用问题
     * 目前特征码就采用ip+user-agent进行md5计算的值为依赖
     * @param ip 计算特征码所需ip
     * @param agent 计算特征码所需agent
     * @param jti 客户端携带的jwt中的特征码
     * @return 检查盗用结果
     */
    private boolean checkSteal(String ip,String agent,String jti){
        String mix = ip+agent;
        System.out.println(MD5.encrypt(mix));
        return false;
    }

    /**
     * 进一步解析token
     * @param token 被解析的token
     * @return 解析结果,null为非法
     */
    private Claims parseToken(String token){
        token = token.substring(wswProperties.getJwtPrefix().length()).trim();
        log.debug("jwt: {}",token);
        Claims claims;
        try {
            claims = JWTs.parse(token, wswProperties.getPrivateKey());
        }catch (Exception e){
            log.warn("非法jwt: {}",token);
            return null;
        }
        log.debug("jwt解析结果: {}",claims);
        return claims;
    }


    /**
     * 快速失败，拒绝服务
     * @param result 返回的数据
     * @param response 本次响应对象
     * @return 响应结果
     */
    private Mono<Void> reject(JsonResult result,ServerHttpResponse response){
        String json = new Gson().toJson(result);
        return response.writeWith(Mono.just(response.bufferFactory().wrap(json.getBytes())));
    }



    /**
     * 从请求头中获取token
     * @param headers 请求头
     * @return 获取结果
     */
    private String getToken(HttpHeaders headers){
        return headers.getFirst(HEADER_AUTHORIZATION);
    }



    /**
     * 尝试获取真实ip地址
     * @param request 本次请求
     * @return ip地址
     */
    private String getIp(ServerHttpRequest request){
        // 尝试从X-Forwarded-For头中获取IP地址
        String ipAddress = request.getHeaders().getFirst("X-Forwarded-For");

        // 如果X-Forwarded-For不存在或为空，则尝试从X-Real-IP头中获取
        if (ipAddress == null || ipAddress.isEmpty()) {
            ipAddress = request.getHeaders().getFirst("X-Real-IP");
        }

        // 如果仍然为空，则回退到getRemoteAddress（这通常是代理服务器的IP）
        if (ipAddress == null || ipAddress.isEmpty()) {
            // 注意：getRemoteAddress()返回的是SocketAddress，需要转换为InetSocketAddress
            Optional<InetSocketAddress> remoteAddress = Optional.ofNullable(request.getRemoteAddress());
            ipAddress = remoteAddress.map(InetSocketAddress::getHostString).orElse("Unknown IP");
        }

        // 如果X-Forwarded-For头存在并且包含多个IP（由逗号分隔），则通常第一个IP是客户端的IP
        if (ipAddress.contains(",")) {
            ipAddress = ipAddress.split(",")[0].trim();
        }

        return ipAddress;
    }

    /**
     * 放行请求
     * @param permitUri 放行路径
     * @param path 请求路径
     * @return 放行结果
     */
    private boolean release(List<String> permitUri, String path){
        boolean flag = false;
        for (String uri : permitUri) {
            if (path.startsWith(uri)) {
                flag = true;
                break;
            }
        }
        return flag;
    }

}
